/* Useage : 
 * ./main16
 * ./main16 <n_bits> <n_tests> <bit variance>
 * Where:
 *      <n_bits> is the nominal number of bits in each number
 *      <n_tests> is the number of times to do a multiply
 *      <bit variance> is the maximum number of bits to be allowed to be 
 *                     subtracted off the maximum number of bits
 */

#include <stdio.h>
#include <stdlib.h>
#include <gmp.h>
#include <sys/time.h>
#include <time.h>
#include <stdint.h>

#define NAILS 0
#define RAND_MOD 2048
#define RAND_THRESHOLD 511
#define ORDER -1
#define ENDIAN 0

extern void Product16(uint16_t *a, uint16_t *b, uint16_t *c,
		      size_t sa, size_t sb, size_t *sc);

int main(int argc, char **argv) {
    int number_of_bits = 3200;
    int number_of_tests = 5000;
    int bit_variance = 0;
    size_t *x_size, *y_size, *z_size;
    int i,j;
    char * x_str, * y_str;
    mpz_t * x_gmp, * y_gmp, *z_gmp, tmp;
    uint16_t ** x, **y, **z;
    int num_correct = 0, num_wrong = 0;
    struct timeval start, end;
    double custom_time, gmp_time;
    
    if(argc >3) {
        number_of_bits = atoi(argv[1]);
        number_of_tests = atoi(argv[2]);
        bit_variance = atoi(argv[3]);
    }

    /* allocate the initial arrays */
    x_gmp = malloc(sizeof(mpz_t) * number_of_tests);
    y_gmp = malloc(sizeof(mpz_t) * number_of_tests);
    z_gmp = malloc(sizeof(mpz_t) * number_of_tests);
    x = malloc(sizeof(uint16_t *) * number_of_tests);
    y = malloc(sizeof(uint16_t *) * number_of_tests);
    z = malloc(sizeof(uint16_t *) * number_of_tests);
    x_size = malloc(sizeof(size_t) * number_of_tests);
    y_size = malloc(sizeof(size_t) * number_of_tests);
    z_size = malloc(sizeof(size_t) * number_of_tests);
    x_str = (char *) malloc((1 + number_of_bits)*sizeof(char)); 
    y_str = (char *) malloc((1 + number_of_bits)*sizeof(char)); 

    /* Setup the numbers */
    srand ( time(NULL));
    for(i = 0; i < number_of_tests; i++) {
        /* generate the random numbers */
        for(j = 0; j < number_of_bits; j++){
            x_str[j] = ((rand()%RAND_MOD) > RAND_THRESHOLD) ? '1' : '0';
            y_str[j] = ((rand()%RAND_MOD) > RAND_THRESHOLD) ? '1' : '0';
        }

        /* Deal with the variance. If i is even, (possibly) truncate x,
         * otherwise, (possibly) truncate y */
        if(0 == i % 2) {
            x_str[number_of_bits - (int)(bit_variance * (double)(rand()/(double)RAND_MAX))] = '\0';
            y_str[number_of_bits] = '\0';
        } else {
            y_str[number_of_bits - (int)(bit_variance * (double)(rand()/(double)RAND_MAX))] = '\0';
            x_str[number_of_bits] = '\0';
        }

        /* setup the mpz numbers */
        mpz_init(x_gmp[i]);
        mpz_init(y_gmp[i]);
        mpz_init(z_gmp[i]);
        mpz_set_str(x_gmp[i], x_str, 2 /* base 2 number */); 
        mpz_set_str(y_gmp[i], y_str, 2 /* base 2 number */);

        /* setup the numbers for the custom multiplication */
        x[i] = mpz_export(NULL, &(x_size[i]), ORDER, sizeof(uint16_t), ENDIAN, NAILS, x_gmp[i]);
        y[i] = mpz_export(NULL, &(y_size[i]), ORDER, sizeof(uint16_t), ENDIAN, NAILS, y_gmp[i]);
        z_size[i] = x_size[i] + y_size[i];
        z[i] = malloc(z_size[i] * sizeof(uint16_t));
    }
    free(x_str);
    free(y_str);

    /* now, time the custom routine */
    gettimeofday(&start, NULL);
    for(j = 0; j < number_of_tests; j++){
        Product16(x[j], y[j], z[j], x_size[j], y_size[j], z_size + j);
    }
    gettimeofday(&end, NULL);
    custom_time = (end.tv_sec - start.tv_sec) + ((end.tv_usec - start.tv_usec) / 1000000.0);

    /* now, time gmp */
    gettimeofday(&start, NULL);
    for(j = 0; j < number_of_tests; j++){
        mpz_mul(z_gmp[j], x_gmp[j], y_gmp[j]);
    }
    gettimeofday(&end, NULL);
    gmp_time = (end.tv_sec - start.tv_sec) + ((end.tv_usec - start.tv_usec) / 1000000.0);


    /* compare the results */
    for(j = 0; j < number_of_tests; j++){
        mpz_init(tmp);
        mpz_import(tmp, z_size[j], ORDER, sizeof(uint16_t), ENDIAN, NAILS, z[j]);

        if(mpz_cmp(tmp, z_gmp[j]) == 0)
            num_correct ++;
        else
            num_wrong ++;
        mpz_clear(tmp);
    }

    printf("============================================================\n");
    printf("\n");
    printf("Number of bits per number: %d\n", number_of_bits);
    printf("Bit variance: %d\n", bit_variance);
    printf("Number of multiplications done: %d\n", number_of_tests);
    printf("Number of correct multiplication: %d\n", num_correct);
    printf("Number of wrong multiplication: %d\n", num_wrong);
    printf("Total time for custom multiplication:  %lf seconds\n", custom_time);
    printf("Total time for gmp multiplication:  %lf seconds\n", gmp_time);
    printf("Slowdown: %lf \n", custom_time / gmp_time);
    printf("\n");
    printf("============================================================\n");
   
    /* cleanup */
    for(i = 0; i < number_of_tests; i++) {
        mpz_clear(x_gmp[i]); 
        mpz_clear(y_gmp[i]); 
        mpz_clear(z_gmp[i]);
        free(x[i]);
        free(y[i]);
        free(z[i]);
    }
    free(x_gmp);
    free(y_gmp);
    free(z_gmp);
    free(x);
    free(y);
    free(z);
    free(x_size);
    free(y_size);
    free(z_size);
    
    return 0;
}
